package com.blue.mountain.mcpsdkjavaserversample;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.McpServerFeatures;
import io.modelcontextprotocol.server.McpSyncServer;
import io.modelcontextprotocol.server.transport.StdioServerTransportProvider;
import io.modelcontextprotocol.spec.McpSchema;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
 * MCP同步服务器示例，基于MCP Java SDK 0.10.0版本，官网的示例基于0.07.0版本，这两个版本在类名上区别较大
 * 包含对工具、资源、提示的详细注释
 */
public class McpSyncServerSample {


    public static void main(String[] args) {
        // 创建基于标准输入输出的传输提供者，用于服务器通信
        StdioServerTransportProvider transportProvider = new StdioServerTransportProvider(new ObjectMapper());
        // 创建并配置MCP同步服务器
        McpSyncServer syncServer = McpServer.sync(transportProvider)
                .serverInfo("my-server", "1.0.0")
                .capabilities(McpSchema.ServerCapabilities.builder()
                        .resources(true, true) //  启用资源支持
                        .tools(true) //  启用工具支持
                        .prompts(true) //  启用提示词符支持
                        .logging() //  启用日志记录
                        .build())
                .build();
        // 添加工具、资源、提示
        syncServer.addTool(syncToolSpecification());
        syncServer.addResource(syncResourceSpecification());
        syncServer.addPrompt(syncPromptSpecification());
    }

    /**
     * 构建工具规范实例
     */
    private static McpServerFeatures.SyncToolSpecification  syncToolSpecification() {
        /*
          tool -工具定义，包括名称、描述和参数模式
          call-实现工具逻辑的函数，接收参数并返回结果。该函数的第一个参数是McpAsyncServerExchange，服务器可以在此基础上与连接的客户端进行交互。第二个参数是工具参数的映射。
         */
        McpServerFeatures.SyncToolSpecification syncToolSpecification = new McpServerFeatures.SyncToolSpecification(
                /*
                  name—工具的唯一标识符。在调用工具时使用此名称。
                  description——人类可读的工具功能描述。这可以被客户用来提高法学硕士对可用工具的理解。
                  inputSchema——一个JSON Schema对象，在调用该工具时描述参数的预期结构。这允许客户端在将工具参数发送到服务器之前验证它们。
                 */
                new McpSchema.Tool("calculator", "计算工具", new McpSchema.JsonSchema("object", Map.of(
                        "operation", "String",
                        "a", "number",
                        "b", "number"
                ), null, null, null, null)),
                /*
                  call函数接收两个参数：McpAsyncServerExchange和参数映射。
                  McpAsyncServerExchange对象包含与当前连接的远程客户端的通信上下文。通过它可以发送消息给客户端，并获取客户端发送的消息。
                  参数映射包含工具调用的参数。
               * exchange 参数提供了与客户端交互的功能，具体功能包括：
               * 1. 发送消息给客户端：通过 exchange.sendMessage() 方法可以主动向客户端发送消息。
               * 2. 获取客户端信息：通过 exchange.getClientInfo() 可获取客户端的元数据（如ID、能力等）。
               * 3. 控制会话上下文：exchange 允许在多个工具调用之间维护状态或上下文信息。
               * 4. 资源管理：支持注册和访问资源，例如 exchange.registerResource() 来注册资源供客户端使用。
               * 5. 日志记录：通过 exchange.getLogger() 获取日志记录器，用于输出调试或运行时信息。
               * 6. 错误处理：可通过 exchange.reportError() 向客户端报告错误并进行异常处理。
               */
                (exchange, arguments) -> {
                    // result是一个包含工具输出的列表。Content有三种类型：文本、图像和嵌入资源。
                    List<McpSchema.Content> result = new ArrayList<>();
                    String operation = String.valueOf(arguments.get("operation"));
                    double a = ((Number) arguments.get("a")).doubleValue();
                    double b = ((Number) arguments.get("b")).doubleValue();
                    double calculationResult = 0;

                    switch (operation) {
                        case "add":
                            calculationResult = a + b;
                            break;
                        case "subtract":
                            calculationResult = a - b;
                            break;
                        case "multiply":
                            calculationResult = a * b;
                            break;
                        case "divide":
                            if (b == 0) {
                                // 错误信息用TextContent对象返回
                                result.add(new McpSchema.TextContent("错误：除数不能为零"));
                                return new McpSchema.CallToolResult(result, true);
                            }
                            calculationResult = a / b;
                            break;
                        default:
                            result.add(new McpSchema.TextContent("错误：未知操作"));
                    }
                    result.add(new McpSchema.TextContent("计算结果: " + calculationResult));
                    /*
                      服务器对来自客户机的工具/调用请求的响应。
                        content——表示工具输出的内容项列表。每个项可以是文本text、图像image或嵌入式资源embedded resource。
                        isError—如果为true，表示工具执行失败，内容包含错误信息。如果为false或不存在，则表示执行成功。
                     */
                    return new McpSchema.CallToolResult(result, false);
                }
        );
        return syncToolSpecification;
    }

    /**
     * 构建提示规范实例
     */
    private static McpServerFeatures.SyncPromptSpecification syncPromptSpecification() {
        /*
            prompt -包含名称和描述的提示定义。
            promptHandler -处理提示请求并返回提示词的函数，提示词的组装就在这里进行。该函数的第一个参数是McpSyncServerExchange，服务器可以在此基础上与连接的客户端进行交互。第二个参数是一个McpSchema.GetPromptRequest。
         */
        McpServerFeatures.SyncPromptSpecification syncPromptSpecification = new McpServerFeatures.SyncPromptSpecification(
                /*
                  Prompt:
                  服务器提供的提示或提示模板。
                  name—提示词或提示词模板的名称。
                  description -此提示词的描述，可选。
                  arguments -用于模板化提示词的参数列表，参数类型是PromptArgument。
                 */
                new McpSchema.Prompt("echo", "回显输入文本", List.of(new McpSchema.PromptArgument("text", "输入文本", true))),
                /*
                    request：GetPromptRequest，name是要获取的提示词的名称，arguments是给模板提示词的参数（提示词可能是一个模板）
                 */
                (exchange, request) -> {
                    // 处理提示词请求，组装提示词
                    /*
                       描述作为提示的一部分返回的消息。这类似于‘ SamplingMessage ’，但也支持从MCP服务器嵌入资源。包含：
                       role—对话中消息和数据的发送者或接收者。Role是一个枚举，定义了两种角色类型：USER和ASSISTANT。个人理解，USER表示用户输入，ASSISTANT表示服务器的回复。
                       content -类型为McpSchema.Content的消息的内容。有三种类型：文本、图像和嵌入资源。
                     */
                    List<McpSchema.PromptMessage> messages = new ArrayList<>();
                    request.arguments().forEach((key, value) -> messages.add(new McpSchema.PromptMessage(McpSchema.Role.USER, new McpSchema.TextContent(String.valueOf(value)))));
                    /*
                        GetPromptResult：是服务器对来自客户机的提示/获取请求的响应。包含：
                        description—提示词的可选描述。
                        messages -作为提示词的一部分显示的消息列表。
                     */
                    return new McpSchema.GetPromptResult("为用户生成的回显提示", messages);
                }
        );
        return syncPromptSpecification;
    }

    /**
     * 构建资源规范实例
     */
    private static McpServerFeatures.SyncResourceSpecification  syncResourceSpecification() {
        /*
          resource -资源定义，包括名称、描述和MIME类型。
          readHandler -处理资源读请求的函数。该函数的第一个参数是McpSyncServerExchange，服务器可以在此基础上与连接的客户端进行交互。第二个参数是一个mcpschema . readresourcerrequest。
         */
        McpServerFeatures.SyncResourceSpecification syncResourceSpecification = new McpServerFeatures.SyncResourceSpecification(
                /*
                    服务器能够读取的已知资源。包括：
                    uri -该资源的唯一标识符。
                    name -该资源的人类可读名称。可以被客户端用来填充UI元素。
                    description -该资源所代表内容的描述。这可以被客户用来提高LLM对可用资源的理解。它可以被认为是对模型的“提示”。
                    mimeType -此资源的MIME类型（如果已知）。
                    注解——客户端可选的注解。客户端可以使用注解来告知对象是如何使用或显示的。
                 */
                new McpSchema.Resource("custom://resource", "示例资源", "这是一个示例资源", "text/plain", null),
                /*
                    request：ReadResourceRequest，包含：uri是要读取的资源的唯一标识符。
                 */
                (exchange, request) -> {
                    // 处理资源读取请求，request中包含资源的URI，这个函数的逻辑就是读取这个资源、处理资源的内容并返回给客户端
                    List<McpSchema.ResourceContents> contents = new ArrayList<>();
                    String content = "这是资源内容示例";
                    contents.add(new McpSchema.TextResourceContents("custom://resource/content", "text/plain", content));
                    /*
                        ReadResourceResult：是服务器对来自客户机的资源读取请求的响应。包含一个内容列表。
                        contents -要返回给客户端的资源内容。是McpSchema.ResourceContents对象。
                        McpSchema.ResourceContents是一个封闭接口，有两种实现：TextResourceContents和BlobResourceContents，分别表示文本和二进制数据。
                        BlobResourceContents内的blob是必须是标识资源二进制数据的base64编码字符串，只有当资源实际上可以表示为二进制数据（而不是文本）时，才必须设置此选项。
                     */
                    return new McpSchema.ReadResourceResult(contents);
                }
        );

        return syncResourceSpecification;
    }

}


